#ifndef JNI_API_H
#define JNI_API_H

#define JNI_Err_Base	80000

// jni callback error base
#define JCB_Err_Base	(JNI_Err_Base + 1000)
#define JCB_Err_Set		(JCB_Err_Base + 1)


#include <jni.h>
#include <string>
#include <vector>

class JniHelper
{
public:
	static jobject c2j_bool(JNIEnv* env, bool value);
	static jobject c2j_int(JNIEnv* env, int value);
	static jobject c2j_float(JNIEnv* env, float value);
	static jstring c2j_string(JNIEnv* env, const char* value);
	static jbyteArray c2j_bytes(JNIEnv* env, unsigned char* byteArray, int numberOfBytes);
	static jshortArray c2j_shorts(JNIEnv* env, short* shortArray, int numberOfShorts);
	static jintArray c2j_ints(JNIEnv* env, int* byteArray, int numberOfInts);

	static bool j2c_bool(JNIEnv* env, jobject obj);
	static int j2c_int(JNIEnv* env, jobject obj);
	static float j2c_float(JNIEnv* env, jobject obj);
	static std::string j2c_string(JNIEnv* env, jstring jstr);
	static int j2c_bytes(JNIEnv* env, jbyteArray byteArray, unsigned char* buf, int len);
	static int j2c_shorts(JNIEnv* env, jshortArray shortArray, short* buf, int len);
	static int j2c_ints(JNIEnv* env, jintArray array, int* buf, int len);

public:
	static jclass getClassByName(JNIEnv* env, const char* name);
	static jclass getClassByObject(JNIEnv* env, jobject obj);
	static jfieldID getFieldID(JNIEnv*env, jclass cls, const char* name, const char* sig);

public:
	static jint getIntField(JNIEnv* env, jobject obj, jfieldID fid);
	static jobject getObjectField(JNIEnv* env, jobject obj, jfieldID fid);
	static jchar getCharField(JNIEnv* env, jobject obj, jfieldID fid);
	static jshort getShortField(JNIEnv* env, jobject obj, jfieldID fid);
	static jboolean getBooleanField(JNIEnv* env, jobject obj, jfieldID fid);
	static jlong getLongField(JNIEnv* env, jobject obj, jfieldID fid);

	static void setIntField(JNIEnv* env, jobject obj, jfieldID fid, jint result);
	static void setObjectField(JNIEnv* env, jobject obj, jfieldID fid, jobject result);
	static void setCharField(JNIEnv* env, jobject obj, jfieldID fid, jchar result);
	static void setShortField(JNIEnv* env, jobject obj, jfieldID fid, jshort result);
	static void setBooleanField(JNIEnv* env, jobject obj, jfieldID fid, jboolean result);
	static void setLongField(JNIEnv* env, jobject obj, jfieldID fid, jlong result);
};

class JniOutputBuilder
{
public:
	JniOutputBuilder(JNIEnv* env);

public:
	jobjectArray getOutput();
	JniOutputBuilder& add(jobject obj);
	JniOutputBuilder& add(int value);
	JniOutputBuilder& add(float value);
	JniOutputBuilder& add(bool value);
	JniOutputBuilder& add(std::string value);
	JniOutputBuilder& add(unsigned char* byteArray, int len);
	JniOutputBuilder& add(short* shortArray, int len);
	JniOutputBuilder& add(int* intArray, int len);

protected:
	std::vector<jobject> _objs;
	JNIEnv* m_env;
};

extern JavaVM* g_vm;

//Global callback
class JniCallbackGlobal
{
public:
	JniCallbackGlobal()
	{
		m_obj = 0;
	}

	virtual bool set(JNIEnv* env, jobject obj)
	{
		bool result = false;

		do 
		{
			if(obj == 0 || env == 0)
				break;

			m_cls = env->GetObjectClass(obj);

			if(m_cls == 0)
				break;

			result = init_jni(env);

			if(result)
			{
				if(m_obj)
					(env)->DeleteGlobalRef(m_obj);
				m_obj = (env)->NewGlobalRef(obj);
			}

		} while (false);

		return result;
	}

	virtual void unset(JNIEnv* env)
	{
		if(m_obj)
		{
			(env)->DeleteGlobalRef(m_obj);
			m_obj = 0;
		}
	}


protected:
	virtual ~JniCallbackGlobal(){}

	virtual bool init_jni(JNIEnv* env) = 0;

	bool before(JNIEnv** env)
	{
		bool result = false;

		do 
		{
			// double check it's all ok
			int getEnvStat = g_vm->GetEnv((void **)env, JNI_VERSION_1_6);

			if (getEnvStat != JNI_OK)
			{
				//not attached
				if (getEnvStat == JNI_EDETACHED)
				{
					//Failed to attach
					if (g_vm->AttachCurrentThread((void **) env, NULL) != JNI_OK)
						break;
				}
				else
				{
					break;
				}
			}

			result = true;

		} while (false);

		return result;
	}

	void after(JNIEnv* env)
	{
		if (env->ExceptionCheck())
			env->ExceptionDescribe();

		g_vm->DetachCurrentThread(); 
	}

protected:
	jobject m_obj;
	jclass m_cls;
};

class JniCallbackLocal
{
public:
	bool set(JNIEnv* env, jobject obj)
	{
		bool result = false;

		do 
		{
			if(obj == 0 || env == 0)
				break;

			m_cls = env->GetObjectClass(obj);

			if(m_cls == 0)
				break;

			m_obj = obj;
			m_env = env;	

			result = init_jni();

		} while (false);

		return result;
	}

protected:
	virtual bool init_jni() = 0;

protected:
	JNIEnv* m_env;
	jobject m_obj;
	jclass m_cls;
};

class JniCallbackGlobalNew : public JniCallbackGlobal
{
public:
	virtual ~JniCallbackGlobalNew(){}

public:
	virtual bool set(JNIEnv* env, jobject obj)
	{
		bool result = false;
		
		do 
		{
			result = JniCallbackGlobal::set(env, obj);

			if(!result)
				break;

			JniHelper::setLongField(env, obj, JniHelper::getFieldID(env, JniHelper::getClassByObject(env, obj), "_p", "J"), (jlong)this);

		} while (false);

		return result;
	}
};


#endif //!JNI_API_H
